package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.netflix.discovery.converters.Auto;
import org.apache.commons.lang.StringUtils;

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;

import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.lang.model.type.ReferenceType;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @package com.leyou.search.service
 * @description:
 * @author: 86157
 * @date 2019/6/30 18:28
 * @version: V1.0
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    /**
     * 将查询到的spu数据转为Goods对象
     */
    public Goods buildGoods(SpuDTO spu) {
        // 1 商品相关搜索信息的拼接：名称、分类、品牌、规格信息等
        // 1.1 分类
        String categoryNames = itemClient.queryCategoryById(spu.getCategoryIds()).stream().map(CategoryDTO::getName).collect(Collectors.joining(","));
        //1.2品牌
        BrandDTO brand = itemClient.queryBrandById(spu.getBrandId());
        //1.3spu名称
        String all = spu.getName() + categoryNames + brand.getName();

        //2.查询spu下所有sku的json数组
        List<SkuDTO> skuList = itemClient.querySkuBySpuId(spu.getId());
        // 准备一个集合，用map来代替sku，只需要sku中的部分数据
        List<Map<String, Object>> skuMap = new ArrayList<>();

        for (SkuDTO skuDTO : skuList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            map.put("title", skuDTO.getTitle());
            map.put("price", skuDTO.getPrice());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            //将sku数据封装到list中
            skuMap.add(map);
        }

        //3.当前spu下所有sku的价格的集合
        Set<Long> price = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //4.当前spu规格参数
        HashMap<String, Object> specs = new HashMap<>();

        // 4.1 获取规格参数key，来自于SpecParam中当前分类下的需要搜索的规格
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, spu.getCid3(), true);

        //4.2 获取规格参数值，来自于spuDetail
        SpuDetailDTO spuDetail = itemClient.querySpuDetailById(spu.getId());

        //4.2.1通用规格参数值
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);

        //4.2.2特有规格参数值
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        for (SpecParamDTO specParam : specParams) {
            //获取规格参数名称
            String key = specParam.getName();
            //获取规格参数值
            Object value = null;

            //判断是否是通用规格
            if (specParam.getSearching()) {
                //通用规格
                value = genericSpec.get(specParam.getId());
            } else {
                value = specialSpec.get(specParam.getId());
            }

            //判断是否数字类型
            if (specParam.getNumeric()) {
                //是数字类型，分段
                value = chooseSegment(value, specParam);
            }

            //添加到specs
            specs.put(key, value);
        }


        //得到goods数据
        Goods goods = new Goods();
        // 从spu对象中拷贝与goods对象中属性名一致的属性
        goods.setBrandId(spu.getBrandId());
        goods.setCategoryId(spu.getCid3());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setCreateTime(spu.getCreateTime().getTime());
        goods.setSkus(JsonUtils.toString(skuMap));// spu下的所有sku的JSON数组
        goods.setSpecs(specs); // 当前spu的规格参数
        goods.setPrice(price); // 当前spu下所有sku的价格的集合
        goods.setAll(all);// 商品相关搜索信息的拼接：标题、分类、品牌、规格信息等

        return goods;
    }


    /**
     * 取值范围
     *
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";

        //保存数值段
        for (String senment : p.getSegments().split(",")) {
//            0 - 1.0, 1.0 - 1.5, 1.5 - 2.0, 2.0 - 2.5

            String[] segs = senment.split("-");
            //获取取值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }

            //判断是否在取值范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = senment + p.getUnit();
                }
                break;
            }

        }
        return result;
    }


    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    public PageResult<GoodsDTO> search(SearchRequest request) {
        //健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.组织条件
        // 2.0.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //2.1关键字的match匹配查询
        QueryBuilder basicQuest = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuest);
        //2.2分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        //3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.解析分页数据
        long total = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> goods = result.getContent();

        //转换
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goods, GoodsDTO.class);

        return new PageResult<>(total, totalPages, goodsDTOS);
    }


    /**
     * 查询过滤项
     *
     * @param request
     * @return
     */

    public Map<String, List<?>> queryFilters(SearchRequest request) {
        //1.创建过滤项集合
        Map<String, List<?>> filterList = new LinkedHashMap<>();
        //2.创建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //  2.1获取查询条件
        QueryBuilder basicBuilder = buildBasicQuery(request);
        queryBuilder.withQuery(basicBuilder);

        // 2.2.减少查询结果(这里只需要聚合结果)
        // 每页显示1个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //3.聚合条件

        //3.1分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));

        //3.2品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //4.查询并解析结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        Aggregations aggregations = result.getAggregations();


        //4.1获取分类聚合
        LongTerms cTerms = aggregations.get(categoryAgg);

        List<Long> idList = handlerCategoryAgg(cTerms, filterList);

        // 4.2.获取品牌聚合
        LongTerms bTerms = aggregations.get(brandAgg);
        handlerBrandAgg(bTerms, filterList);

        //5.规格参数处理
        if (idList != null && idList.size() == 1) {
            //处理规格,参数：分类id，查询条件，过滤项集合
            handlerSpecAgg(idList.get(0), basicBuilder, filterList);
        }

        return filterList;
    }

    /**
     * 规格参数处理
     *
     * @param cid
     * @param basicBuilder
     * @param filterList
     */
    private void handlerSpecAgg(Long cid, QueryBuilder basicBuilder, Map<String, List<?>> filterList) {
        //查询分类下需要分类过滤的规格参数名
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, cid, true);

        //聚合规格参数
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //获取查询条件
        queryBuilder.withQuery(basicBuilder);
        //减少查询结果，这里只需要聚合结果
        //每页显示一个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //显示空的资源
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //聚合条件
        for (SpecParamDTO param : specParamDTOS) {
            //获取param的名称，作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        //查询并获得聚合结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        //解析聚合结果
        for (SpecParamDTO param : specParamDTOS) {
            //获取param的name
            String name = param.getName();
            Terms terms = aggregations.get(name);

            //获取聚合结果
            List<String> collect = terms.getBuckets().
                    stream().map(Terms.Bucket::getKeyAsString).filter(StringUtils::isNotEmpty).collect(Collectors.toList());

            filterList.put(name, collect);
        }

    }


    /**
     * 获取品牌聚合
     */
    private void handlerBrandAgg(LongTerms cTerms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合
        List<Long> idList = cTerms.getBuckets().stream().map(LongTerms.Bucket::getKeyAsNumber).
                map(Number::longValue).collect(Collectors.toList());

        // 根据id集合查询品牌
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(idList);
        //存入map
        filterList.put("品牌", brandDTOS);
    }

    /**
     * 获取分类聚合
     *
     * @param
     * @return
     */
    private List<Long> handlerCategoryAgg(LongTerms terms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合
        List<Long> idList = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id集合查询分类
        List<CategoryDTO> categoryList = itemClient.queryCategoryById(idList);
        // 存入map
        filterList.put("分类", categoryList);
        return idList;
    }


    /**
     * //构建查询条件的代码封装成一个方法
     *
     * @param request
     * @return
     */
    private QueryBuilder buildBasicQuery(SearchRequest request) {
        /*// 构建基本的match查询
        return QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND);*/
        // 构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 构建过滤条件
        Map<String, String> filters = request.getFilter();
        if (!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                // 获取过滤条件的key
                String key = entry.getKey();
                // 规格参数的key要做前缀specs.
                if ("分类".equals(key)) {
                    key = "categoryId";
                } else if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "specs." + key;
                }
                // value
                String value = entry.getValue();
                // 添加过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(key, value));
            }
        }

        return queryBuilder;
    }

    /**
     * 新增索引库
     *
     * @param id
     */
    public void addIndex(Long id) {
        //查询spu
        SpuDTO spuDTO = itemClient.querySpuById(id);
        // 创建goods
        Goods goods = buildGoods(spuDTO);
        //新增索引库
        goodsRepository.save(goods);
    }

    /**
     * 删除索引库
     * @param id
     */
    public void deleteById(Long id) {
        goodsRepository.deleteById(id);
    }
}